/*
 * Unidata Platform
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *
 * Commercial License
 * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *
 * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 * For clarification or additional options, please contact: info@unidata-platform.com
 * -------
 * Disclaimer:
 * -------
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 */
package org.unidata.mdm.rest.draft.service;

import java.util.Collections;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.unidata.mdm.draft.context.DraftPublishContext;
import org.unidata.mdm.draft.context.DraftQueryContext;
import org.unidata.mdm.draft.context.DraftRemoveContext;
import org.unidata.mdm.draft.context.DraftUpsertContext;
import org.unidata.mdm.draft.dto.DraftPublishResult;
import org.unidata.mdm.draft.dto.DraftQueryResult;
import org.unidata.mdm.draft.dto.DraftUpsertResult;
import org.unidata.mdm.draft.service.DraftService;
import org.unidata.mdm.rest.draft.converter.DraftConverter;
import org.unidata.mdm.rest.draft.ro.DraftPublishRequestRO;
import org.unidata.mdm.rest.draft.ro.DraftQueryRequestRO;
import org.unidata.mdm.rest.draft.ro.DraftRemoveRequestRO;
import org.unidata.mdm.rest.draft.ro.DraftUpsertRequestRO;
import org.unidata.mdm.rest.system.ro.ErrorResponse;
import org.unidata.mdm.rest.system.ro.RestResponse;
import org.unidata.mdm.rest.system.service.AbstractRestService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;

/**
 * @author Alexander Malyshev
 * @author Alexey Tsarapkin
 */
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
@Path("/")
public class DraftRestService extends AbstractRestService {

    private final DraftService draftService;

    public DraftRestService(final DraftService draftService) {
        this.draftService = draftService;
    }

    @GET
    @Path("types")
    @Operation(
            description = "Returns list of draft provider types, currently registered by the system.",
            method = HttpMethod.GET,
            responses = {
                    @ApiResponse(content = @Content(schema = @Schema(implementation = RestResponse.class)), responseCode = "200"),
                    @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
            }
    )
    public Response types() {
        return ok(new RestResponse<>(draftService.providers()));
    }

    @POST
    @Path("count")
    @Operation(
            description = "Get drafts count for request",
            method = "GET",
            requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = DraftQueryRequestRO.class)), description = "DraftResult request"),
            responses = {
                    @ApiResponse(content = @Content(schema = @Schema(implementation = RestResponse.class)), responseCode = "200"),
                    @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
            }
    )
    public Response draftsCount(DraftQueryRequestRO request) {

        DraftQueryContext dqc = DraftQueryContext.builder()
            .provider(request.getType())
            .subjectId(request.getSubjectId())
            .draftId(request.getDraftId())
            .parentDraftId(request.getParentDraftId())
            .owner(request.getOwner())
            .tags(request.getTags())
            .build();

        return ok(new RestResponse<>(draftService.count(dqc)));
    }

    /**
     * Find drafts for request
     *
     * @return List of drafts
     */
    @POST
    @Path("drafts")
    @Operation(
            description = "Find drafts for request",
            method = "POST",
            parameters = {
                    @Parameter(description = "Select from", in = ParameterIn.QUERY, name = "start"),
                    @Parameter(description = "Select limit", in = ParameterIn.QUERY, name = "limit")
            },
            requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = DraftQueryRequestRO.class)), description = "DraftResult request"),
            responses = {
                    @ApiResponse(content = @Content(schema = @Schema(implementation = RestResponse.class)), responseCode = "200"),
                    @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
            }
    )
    public Response drafts(
            @QueryParam("start") @DefaultValue("0") Integer start,
            @QueryParam("limit") @DefaultValue("10") Integer limit,
            DraftQueryRequestRO request) {

        DraftQueryContext dqc = DraftQueryContext.builder()
                .provider(request.getType())
                .subjectId(request.getSubjectId())
                .draftId(request.getDraftId())
                .parentDraftId(request.getParentDraftId())
                .owner(request.getOwner())
                .tags(request.getTags())
                .start(start)
                .limit(limit)
                .build();

        DraftQueryResult dqr = draftService.drafts(dqc);
        return ok(new RestResponse<>(dqr.getDrafts().stream()
                .map(DraftConverter::from)
                .collect(Collectors.toList())));
    }

    /**
     * Find drafts for request
     *
     * @return List of drafts
     */
    @POST
    @Path("upsert")
    @Operation(
            description = "Upsert draft, create or update display name if draft exists",
            method = "POST",
            requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = DraftUpsertRequestRO.class)), description = "DraftResult request"),
            responses = {
                    @ApiResponse(content = @Content(schema = @Schema(implementation = RestResponse.class)), responseCode = "200"),
                    @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
            }
    )
    public Response upsert(DraftUpsertRequestRO request) {

        DraftUpsertContext ctx = DraftUpsertContext.builder()
            .provider(request.getType())
            .subjectId(StringUtils.defaultString(request.getSubjectId(), StringUtils.EMPTY))
            .draftId(request.getDraftId())
            .parentDraftId(request.getParentDraftId())
            .owner(request.getOwner())
            .description(request.getDescription())
            .tags(request.getTags())
            .parameters(MapUtils.isEmpty(request.getParameters())
                    ? Collections.emptyMap()
                    : request.getParameters()
                        .entrySet()
                        .stream()
                        .collect(Collectors.toMap(Entry::getKey, Entry::getValue)))
            .build();

        DraftUpsertResult result = draftService.upsert(ctx);
        return ok(new RestResponse<>(DraftConverter.from(result.getDraft())));
    }

    /**
     * Find drafts for request
     *
     * @return result
     */
    @POST
    @Path("publish")
    @Operation(
            description = "Publish draft",
            method = "POST",
            requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = DraftPublishRequestRO.class)), description = "DraftResult request"),
            responses = {
                    @ApiResponse(content = @Content(schema = @Schema(implementation = RestResponse.class)), responseCode = "200"),
                    @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
            }
    )
    public Response publish(DraftPublishRequestRO request) {

        DraftPublishContext ctx = DraftPublishContext.builder()
            .draftId(request.getDraftId())
            .force(request.isForce())
            .delete(request.isDelete())
            .build();

        DraftPublishResult result = draftService.publish(ctx);
        return ok(new RestResponse<>(DraftConverter.from(result.getDraft())));
    }

    /**
     * Remove draft for id
     *
     * @return result
     */
    @POST
    @Path("remove")
    @Operation(
            description = "Remove draft",
            method = "POST",
            requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = DraftRemoveRequestRO.class)), description = "DraftResult request"),
            responses = {
                    @ApiResponse(content = @Content(schema = @Schema(implementation = RestResponse.class)), responseCode = "200"),
                    @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
            }
    )
    public Response remove(DraftRemoveRequestRO request) {

        DraftRemoveContext ctx = DraftRemoveContext.builder()
            .provider(request.getType())
            .draftId(request.getDraftId())
            .subjectId(request.getSubjectId())
            .owner(request.getOwner())
            .tags(request.getTags())
            .build();

        return ok(new RestResponse<>(draftService.remove(ctx).getCount()));
    }
}
